package com.lemon.boot.common.service.impl;

import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.lemon.boot.common.service.ElasticsearchService;
import com.lemon.boot.common.utils.ConverterUtil;
import lombok.extern.slf4j.Slf4j;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.sort.SortBuilder;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.core.*;
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.data.elasticsearch.core.query.UpdateQuery;
import org.springframework.data.elasticsearch.core.query.UpdateResponse;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author 李猛
 * @datetime 2024/4/12 10:55
 * @description 类对象
 */
@Slf4j
@Service
public class ElasticsearchServiceImpl implements ElasticsearchService {
    @Resource
    private ElasticsearchRestTemplate elasticsearchRestTemplate;

    @Override
    public boolean createIndex(String index) {
        log.info("ElasticsearchServiceImpl.createIndex.index:{}", index);
        IndexOperations indexOperations = elasticsearchRestTemplate.indexOps(IndexCoordinates.of(index));
        return indexOperations.create();
    }

    @Override
    public <T> boolean add(T t) {
        log.info("ElasticsearchServiceImpl.add.data:{}", t);
        try {
            T save = elasticsearchRestTemplate.save(t);
            return true;
        } catch (RuntimeException e) {
            log.error("ElasticsearchServiceImpl.add.message:{}", e.getMessage());
            return false;
        }
    }

    @Override
    public <T> boolean del(String id, Class<T> clazz) {
        log.info("ElasticsearchServiceImpl.del.id:{}", id);
        try {
            String delete = elasticsearchRestTemplate.delete(id, clazz);
            return true;
        } catch (RuntimeException e) {
            log.error("ElasticsearchServiceImpl.del.message:{}", e.getMessage());
            return false;
        }
    }

    @Override
    public <T> boolean del(Long id, Class<T> clazz) {
        return this.del(String.valueOf(id), clazz);
    }

    @Override
    public <T> boolean edit(T t) {
        log.info("ElasticsearchServiceImpl.update.data:{}", t);
        try {
            //1.通过反射获取对象上的注解的indexName
            Document annotation = t.getClass().getAnnotation(Document.class);
            String indexName = annotation.indexName();

            //2.获取对象的属性
            Map<String, Object> param = ConverterUtil.toMap(t);
            org.springframework.data.elasticsearch.core.document.Document document = org.springframework.data.elasticsearch.core.document.Document.from(param);
            UpdateQuery updateQuery = UpdateQuery.builder(param.get("id").toString()).withDocument(document).build();
            UpdateResponse update = elasticsearchRestTemplate.update(updateQuery, IndexCoordinates.of(indexName));
            return update.getResult().equals(UpdateResponse.Result.UPDATED);
        } catch (RuntimeException e) {
            log.error("ElasticsearchServiceImpl.update.message:{}", e.getMessage());
            return false;
        }
    }

    @Override
    public <T> T get(String id, Class<T> clazz) {
        log.info("ElasticsearchServiceImpl.get.id:{}", id);
        return elasticsearchRestTemplate.get(id, clazz);
    }

    @Override
    public <T> T get(Long id, Class<T> clazz) {
        return this.get(String.valueOf(id), clazz);
    }

    @Override
    public <T> Page<T> page(QueryBuilder queryBuilder, long current, long size, Class<T> clazz) {
        log.info("ElasticsearchServiceImpl.page.current:{},size:{}", current, size);
        return this.page(queryBuilder, null, current, size, clazz);
    }

    @Override
    public <T> Page<T> page(QueryBuilder queryBuilder, SortBuilder<?> sortBuilder, long current, long size, Class<T> clazz) {
        //1.Elasticsearch官方默认最多只能查询10000条数据，当超出 10000的时候，使用滚动查询
        if (current * size > 10000) {
            //throw new CustomException("分页数量过多");
            return this.scroll(queryBuilder, sortBuilder, current, size, clazz);
        }

        //2.查询条件
        NativeSearchQueryBuilder builder = new NativeSearchQueryBuilder()
                .withQuery(queryBuilder)//条件搜索
                .withPageable(PageRequest.of((int) current - 1, (int) size))//分页信息
                .withTrackTotalHits(true);//Elasticsearch官方默认最多只能查询10000条数据，设置为true就会返回真实的命中条数

        //3.排序
        if (sortBuilder != null) {
            builder.withSorts(sortBuilder);
        }

        //4.查询
        SearchHits<T> searchHits = elasticsearchRestTemplate.search(builder.build(), clazz);

        //5.封装分页数据
        Page<T> result = new Page<>(current, size, searchHits.getTotalHits());
        result.setRecords(searchHits.getSearchHits().stream().map(SearchHit::getContent).collect(Collectors.toList()));
        return result;
    }

    @Override
    public <T> Page<T> scroll(QueryBuilder queryBuilder, SortBuilder<?> sortBuilder, long current, long size, Class<T> clazz) {
        //1.获取索引名字
        String indexName = clazz.getAnnotation(Document.class).indexName();
        IndexCoordinates index = IndexCoordinates.of(indexName);

        //2.滚动分页数量10000条，滚动时间1000ms
        final long scrollSize = 10000L;
        final long scrollTimeInMillis = 1000L;

        //3.查询条件
        NativeSearchQueryBuilder builder = new NativeSearchQueryBuilder()
                .withSorts(sortBuilder)//排序
                .withPageable(PageRequest.of(0, (int) scrollSize))//分页
                .withQuery(queryBuilder)//条件搜索
                .withTrackTotalHits(true);//Elasticsearch官方默认最多只能查询10000条数据，设置为true就会返回真实的命中条数

        //4.查询
        SearchScrollHits<T> scrollHits = elasticsearchRestTemplate.searchScrollStart(scrollTimeInMillis, builder.build(), clazz, index);

        //5.滚动ID
        String scrollId = scrollHits.getScrollId();

        //6.数据集合
        List<T> list = scrollHits.getSearchHits().stream().map(SearchHit::getContent).collect(Collectors.toList());

        //7.封装分页数据
        Page<T> result = new Page<>(current, size, scrollHits.getTotalHits());

        //8.滚动分页-起始页
        int page = 1;
        try {
            //9.滚动分页-循环
            while (scrollHits.hasSearchHits() && StringUtils.hasText(scrollId)) {
                log.info("ElasticsearchServiceImpl.scroll.page:{}", page);
                page++;
                scrollHits = elasticsearchRestTemplate.searchScrollContinue(scrollId, scrollTimeInMillis, clazz, index);
                scrollId = scrollHits.getScrollId();
                list.addAll(scrollHits.getSearchHits().stream().map(SearchHit::getContent).collect(Collectors.toList()));
                if ((page * scrollSize) > (current * size)) {
                    break;
                }
            }

            //10.获取分页数据
            result.setRecords(list.stream().skip(current * size - current).limit(size).collect(Collectors.toList()));
        } finally {
            //11.清空滚动ID
            elasticsearchRestTemplate.searchScrollClear(Collections.singletonList(scrollId));
            //12.清空数据，释放堆内存
            list = null;
            //System.gc();
        }
        return result;
    }
}
